package com.yangyang.ralearn.net.serialize;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;

/**
 * 目前支持的序列化类型包括如下：
 * byte，short，char，integer，long，float，double，boolean，string
 * BaseMessageEntity 的派生类（不允许为null，否则序列化会出错！）
 * Collection的派生类（包括List,Set）等。
 * 使用此串化类需要满足以下条件：
 * 1.需要序列化的字段必须不为空（既有意义）
 * 2.需要序列化的BaseMessageEntity派生类需要有无参的构造方法。
 * 3.不支持collection类型的循环嵌套 如 List<set<>>...
 * 4.如果使用collection作为需要序列化的属性，默认的构造函数里需要对collection初始化。
 * 5.自定义的子类一定要实现 GetCmdid这个方法，并且对于每一个不同的message，cmdid要保证
 * 唯一。
 * 
 * 
 * 根据某些特定的注解序列化消息类型，为了提高效率，将相同类型的类做缓存，
 * 第二次不需要再次反射同种类型的对象。
 * 序列化规则：
 * 对于基本类型（包括number,sting,boolean,basemessageentity)直接写需要序列化的属性到内存
 * 中。
 * 对于可迭代的类型（Collection）：首先写长度（Short）到内存中，表示集合的大小。然后按照写
 * 基本类型的方法写数据。
 * 
 * @author yangyang
 * 
 */

/**
 * 针对不同的属性实现不同的写方法
 * 
 * @author yangyang
 * 
 */
interface IWriteValue {
	/**
	 * 写数据到buffer
	 * 
	 * @param buffer
	 * @param value
	 */
	int writeValue(ChannelBuffer buffer, Object value);

	/**
	 * 从buffer中度数据
	 * 
	 * @param buffer
	 * @param value
	 */
	int readValue(ChannelBuffer buffer, ISerizlizable entity);
}

public class SerializeEntity {

	/**
	 * 缓存已经序列化过的类的属性结构
	 */
	private static Map<Integer, Map<Field, IWriteValue>> id2field2method = new HashMap<Integer, Map<Field, IWriteValue>>();
	private static Map<Integer, Map<Field, Integer>> id2field2writableindex = new HashMap<Integer, Map<Field, Integer>>();

	/**
	 * 序列化某些消息类
	 * 
	 * @param entity
	 *            需要序列化的实体类
	 * @param writable
	 *            需要序列化的实体属性注解索引(null 表示所有标记了序列化注解的属性全部序列化，否则只序列化在列表内的属性）
	 * @return
	 */

	public static int serialize(ChannelBuffer buffer, ISerizlizable entity,
			Integer[] writable) {

		generateClassStructureBuffer(entity);

		Map<Field, IWriteValue> field2func = id2field2method.get(entity
				.getKey());
		Map<Field, Integer> field2writableindex = id2field2writableindex
				.get(entity.getKey());

		List<Integer> list = (writable == null || writable.length == 0) ? null
				: Arrays.asList(writable);
		int len = 0;
		for (Field field : field2func.keySet()) {
			if (writable == null || writable.length == 0
					|| list.contains(field2writableindex.get(field))) {
				try {
					int propertyLen = field2func.get(field).writeValue(buffer,
							field.get(entity));
					if (propertyLen < 0) {
						Logger.getRootLogger().error(
								"error serialize property:" + field);
					} else
						len += propertyLen;

				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		// Logger.getRootLogger().info(buffer.readableBytes());
		return len;
	}

	/**
	 * 如果不存在序列化信息缓存，首先是获取缓存信息
	 */
	public static void generateClassStructureBuffer(ISerizlizable entity) {

		if (!id2field2method.containsKey(entity.getKey())) {

			Map<Field, IWriteValue> field2func = new LinkedHashMap<Field, IWriteValue>();
			Map<Field, Integer> field2writableindex = new LinkedHashMap<Field, Integer>();
			id2field2method.put(entity.getKey(), field2func);
			id2field2writableindex.put(entity.getKey(), field2writableindex);

			Field[] allfields = entity.getClass().getDeclaredFields();
			List<Field> fields = new ArrayList<Field>();

			/**
			 * 过滤不需要序列化的属性
			 */
			for (Field field : allfields) {
				field.setAccessible(true);
				if (field.isAnnotationPresent(AutoSerializable.class)) {
					fields.add(field);
				}
			}

			/**
			 * 按照index排序属性
			 */
			Collections.sort(fields, new Comparator<Field>() {
				@Override
				public int compare(Field o1, Field o2) {
					// TODO Auto-generated method stub
					AutoSerializable annotation1 = o1
							.getAnnotation(AutoSerializable.class);
					AutoSerializable annotation2 = o2
							.getAnnotation(AutoSerializable.class);
					return annotation1.index() - annotation2.index();
				}
			});

			for (Field field : fields) {
				AutoSerializable annotation = field
						.isAnnotationPresent(AutoSerializable.class) ? field
						.getAnnotation(AutoSerializable.class) : null;
				field2writableindex.put(field, annotation.index());

				try {
					Object value = field.get(entity);
					if (value instanceof Number || value instanceof Boolean
							|| value instanceof String
							|| value instanceof ISerizlizable
							|| value instanceof Iterable<?>)
						serializeBasicType(field2func, field, annotation, value);
					else {
						Logger.getRootLogger().error(
								"unrecognized property type:" + field);
					}
				} catch (IllegalArgumentException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 生成基本类型的序列化信息
	 * 
	 * @param field2func
	 * @param field
	 * @param annotation
	 * @param value
	 */
	private static void serializeBasicType(Map<Field, IWriteValue> field2func,
			final Field field, AutoSerializable annotation, Object value) {
		if (value instanceof Number) { // 数值类型
			if (annotation.width() <= 0) {
				if (value instanceof Integer) {
					field2func.put(field, new IWriteValue() {

						@Override
						public int writeValue(ChannelBuffer buffer, Object value) {
							// TODO Auto-generated method stub
							buffer.writeInt((Integer) value);
							return Integer.SIZE / Byte.SIZE;
						}

						@Override
						public int readValue(ChannelBuffer buffer,
								ISerizlizable entity) {
							// TODO Auto-generated method stub
							try {
								field.setInt(entity, buffer.readInt());
								return Integer.SIZE / Byte.SIZE;
							} catch (Exception e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
								return -1;
							}

						}
					});

				} else if (value instanceof Short) {
					field2func.put(field, new IWriteValue() {

						@Override
						public int writeValue(ChannelBuffer buffer, Object value) {
							// TODO Auto-generated method stub
							buffer.writeShort((Short) value);
							return Short.SIZE / Byte.SIZE;
						}

						@Override
						public int readValue(ChannelBuffer buffer,
								ISerizlizable entity) {
							// TODO Auto-generated method stub
							try {
								field.setShort(entity, buffer.readShort());
								return Short.SIZE / Byte.SIZE;
							} catch (Exception e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
								return -1;
							}
						}
					});

				} else if (value instanceof Long) {
					field2func.put(field, new IWriteValue() {

						@Override
						public int writeValue(ChannelBuffer buffer, Object value) {
							// TODO Auto-generated method stub
							buffer.writeLong((Long) value);
							return Long.SIZE / Byte.SIZE;
						}

						@Override
						public int readValue(ChannelBuffer buffer,
								ISerizlizable entity) {
							// TODO Auto-generated method stub
							try {
								field.setLong(entity, buffer.readLong());
								return Long.SIZE / Byte.SIZE;
							} catch (Exception e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
								return -1;
							}

						}
					});

				} else if (value instanceof Byte) {
					field2func.put(field, new IWriteValue() {

						@Override
						public int writeValue(ChannelBuffer buffer, Object value) {
							// TODO Auto-generated method stub
							buffer.writeByte((Byte) value);
							return 1;
						}

						@Override
						public int readValue(ChannelBuffer buffer,
								ISerizlizable entity) {
							// TODO Auto-generated method stub
							try {
								field.setByte(entity, buffer.readByte());
								return 1;
							} catch (Exception e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
								return -1;
							}

						}
					});

				} else if (value instanceof Float) {
					field2func.put(field, new IWriteValue() {

						@Override
						public int writeValue(ChannelBuffer buffer, Object value) {
							// TODO Auto-generated method stub
							buffer.writeFloat((Float) value);
							return Float.SIZE / Byte.SIZE;
						}

						@Override
						public int readValue(ChannelBuffer buffer,
								ISerizlizable entity) {
							// TODO Auto-generated method stub
							try {
								field.setFloat(entity, buffer.readFloat());
								return Float.SIZE / Byte.SIZE;
							} catch (Exception e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
								return -1;
							}

						}
					});

				} else if (value instanceof Double) {
					field2func.put(field, new IWriteValue() {

						@Override
						public int writeValue(ChannelBuffer buffer, Object value) {
							// TODO Auto-generated method stub
							buffer.writeDouble((Double) value);
							return Double.SIZE / Byte.SIZE;
						}

						@Override
						public int readValue(ChannelBuffer buffer,
								ISerizlizable entity) {
							// TODO Auto-generated method stub
							try {
								field.setDouble(entity, buffer.readDouble());
								return Double.SIZE / Byte.SIZE;
							} catch (Exception e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
								return -1;
							}

						}
					});

				} else {
					Logger.getRootLogger().error(
							"serialize field error,type:" + field);
				}
			} else {
				switch (annotation.width()) {
				case 1:
					field2func.put(field, new IWriteValue() {

						@Override
						public int writeValue(ChannelBuffer buffer, Object value) {
							// TODO Auto-generated method stub
							buffer.writeByte(((Number) value).byteValue());
							return 1;
						}

						@Override
						public int readValue(ChannelBuffer buffer,
								ISerizlizable entity) {
							// TODO Auto-generated method stub
							try {
								field.set(entity, buffer.readByte());
								return 1;
							} catch (Exception e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
								return -1;
							}

						}
					});
					break;
				case 2:
					field2func.put(field, new IWriteValue() {

						@Override
						public int writeValue(ChannelBuffer buffer, Object value) {
							// TODO Auto-generated method stub
							buffer.writeShort(((Number) value).shortValue());
							return 2;
						}

						@Override
						public int readValue(ChannelBuffer buffer,
								ISerizlizable entity) {
							// TODO Auto-generated method stub
							try {
								field.set(entity, buffer.readShort());
								return 2;
							} catch (Exception e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
								return -1;
							}

						}
					});
					break;
				case 4:
					field2func.put(field, new IWriteValue() {

						@Override
						public int writeValue(ChannelBuffer buffer, Object value) {
							// TODO Auto-generated method stub
							buffer.writeInt(((Number) value).intValue());
							return 4;
						}

						@Override
						public int readValue(ChannelBuffer buffer,
								ISerizlizable entity) {
							// TODO Auto-generated method stub
							try {
								field.set(entity, buffer.readInt());
								return 4;
							} catch (Exception e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
								return -1;
							}

						}
					});
					break;
				case 8:
					field2func.put(field, new IWriteValue() {

						@Override
						public int writeValue(ChannelBuffer buffer, Object value) {
							// TODO Auto-generated method stub
							buffer.writeLong(((Number) value).longValue());
							return 8;
						}

						@Override
						public int readValue(ChannelBuffer buffer,
								ISerizlizable entity) {
							// TODO Auto-generated method stub
							try {
								field.set(entity, buffer.readLong());
								return 8;
							} catch (Exception e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
								return -1;
							}

						}
					});
					break;
				default:
					Logger.getRootLogger().error(
							"serialize field error,invalid bytewidth:"
									+ annotation.width());
				}
			}

		} else if (value instanceof ISerizlizable) {
			field2func.put(field, new IWriteValue() {

				private Class<?> type = null;

				@Override
				public int writeValue(ChannelBuffer buffer, Object value) {
					// TODO Auto-generated method stub
					type = value.getClass();
					int len = serialize(buffer, (ISerizlizable) value, null);
					return len;

				}

				@Override
				public int readValue(ChannelBuffer buffer, ISerizlizable entity) {
					// TODO Auto-generated method stub
					try {
						ISerizlizable instance = (ISerizlizable) type
								.newInstance();
						Map<Field, IWriteValue> field2func = id2field2method
								.get(instance.getKey());
						for (Field field : field2func.keySet()) {
							field2func.get(field).readValue(buffer, instance);
						}
						field.set(entity, instance);
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					return 0;
				}
			});

		} else if (value instanceof Boolean) { // 布尔类型

			field2func.put(field, new IWriteValue() {

				@Override
				public int writeValue(ChannelBuffer buffer, Object value) {
					// TODO Auto-generated method stub
					buffer.writeByte(((Boolean) value) == true ? 1 : 0);
					return 1;
				}

				@Override
				public int readValue(ChannelBuffer buffer, ISerizlizable entity) {
					// TODO Auto-generated method stub
					try {
						field.set(entity, buffer.readByte() == 0 ? false : true);
						return 1;
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
						return -1;
					}

				}
			});

		} else if (value instanceof String) { // 字符串类型

			field2func.put(field, new IWriteValue() {

				@Override
				public int writeValue(ChannelBuffer buffer, Object value) {
					// TODO Auto-generated method stub
					try {
						byte[] membuffer = ((String) value).getBytes("utf8");
						buffer.writeShort(membuffer.length);
						buffer.writeBytes(membuffer);
						return membuffer.length;
					} catch (UnsupportedEncodingException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
						return -1;
					}
				}

				@Override
				public int readValue(ChannelBuffer buffer, ISerizlizable entity) {
					// TODO Auto-generated method stub

					Short len = buffer.readShort();
					byte[] membuffer = new byte[len];
					buffer.readBytes(membuffer, 0, len);
					try {
						field.set(entity, new String(membuffer, "utf8"));
						return len;
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
						return -1;
					}

				}
			});

		} else if (value instanceof Iterable<?>) {
			field2func.put(field, new IWriteValue() {

				private Class<?> type = null;

				@Override
				public int writeValue(ChannelBuffer buffer, Object value) {
					// TODO Auto-generated method stub

					if (!((Iterable<?>) value).iterator().hasNext())
						return 0;
					type = ((Iterable<?>) value).iterator().next().getClass();
					return writeCollection(buffer, (Collection<?>) value);
				}

				@Override
				public int readValue(ChannelBuffer buffer, ISerizlizable entity) {
					if (type == null)
						return 0;
					try {

						Object value = null;
						try {
							value = field.get(entity);
						} catch (Exception e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						readCollection(buffer, (Collection<?>) value, type);

					} catch (SecurityException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					return 0;
				}
			});

		} else {
			Logger.getRootLogger().error("unrecognised property:" + field);
		}
	}

	private static int writeCollection(ChannelBuffer buffer, Collection<?> value) {
		buffer.writeShort(((Collection<?>) value).size()); // 写迭代器长度
		int size = 2;
		for (Object item : ((Collection<?>) value)) {

			int len = 0;
			if (item instanceof Number) {
				if (item instanceof Byte) {
					buffer.writeByte(((Number) item).byteValue());
					len = 1;
				} else if (item instanceof Short) {
					buffer.writeShort(((Number) item).shortValue());
					len = 2;
				} else if (item instanceof Integer) {
					buffer.writeInt(((Number) item).intValue());
					len = 4;
				} else if (item instanceof Long) {
					buffer.writeLong(((Number) item).longValue());
					len = 8;
				} else if (item instanceof Float) {
					buffer.writeFloat(((Number) item).floatValue());
					len = 4;
				} else if (item instanceof Double) {
					buffer.writeDouble(((Number) item).doubleValue());
					len = 8;
				}

			} else if (item instanceof Boolean) {
				buffer.writeByte(((Boolean) item) ? 1 : 0);
				len = 1;

			} else if (item instanceof String) {
				byte[] data = null;
				try {
					data = ((String) item).getBytes("utf8");
				} catch (UnsupportedEncodingException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				len = data.length;
				buffer.writeShort(len);
				buffer.writeBytes(data);
			} else if (item instanceof ISerizlizable) {
				len = serialize(buffer, (ISerizlizable) item, null);

			} else if (item instanceof Collection<?>) {
				Logger.getRootLogger().error(
						"don't support recursive collection!");
				len = -1;
			} else {
				Logger.getRootLogger().error(
						"collection item is not a basic type!");
				len = -1;
			}
			size += len;
		}
		return size;
	}

	@SuppressWarnings("unchecked")
	private static void readCollection(ChannelBuffer buffer,
			Collection<?> collection, Class<?> type) {
		int number = buffer.readShort();
		collection.clear();
		for (int i = 0; i < number; i++) {
			if (type.getSuperclass() == Number.class) {
				if (type == Byte.class) {
					((Collection<Byte>) collection).add(buffer.readByte());
				} else if (type == Short.class) {
					((Collection<Short>) collection).add(buffer.readShort());
				} else if (type == Integer.class) {
					((Collection<Integer>) collection).add(buffer.readInt());
				} else if (type == Long.class) {
					((Collection<Long>) collection).add(buffer.readLong());
				} else if (type == Float.class) {
					((Collection<Float>) collection).add(buffer.readFloat());
				} else if (type == Double.class) {
					((Collection<Double>) collection).add(buffer.readDouble());
				} else {
					Logger.getRootLogger().error(
							"unrecognised number format:" + type);
				}
			} else if (type == Boolean.class) {
				((Collection<Boolean>) collection)
						.add(buffer.readByte() > 0 ? true : false);

			} else if (type == String.class) {
				int len = buffer.readShort();
				byte[] array = new byte[len];
				buffer.readBytes(array, 0, len);
				try {
					((Collection<String>) collection).add(new String(array,
							"utf8"));
				} catch (UnsupportedEncodingException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

			} else if (ISerizlizable.class.isAssignableFrom(type)) {
				try {
					ISerizlizable entity = (ISerizlizable) type.newInstance();
					Map<Field, IWriteValue> field2func = id2field2method
							.get(entity.getKey());
					for (Field field : field2func.keySet()) {
						field2func.get(field).readValue(buffer, entity);
					}
					((Collection<ISerizlizable>) collection).add(entity);
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	public static ISerizlizable deserialize(
			Class<? extends ISerizlizable> type, byte[] data,
			Integer... writable) {

		ISerizlizable entity = null;

		try {
			entity = type.newInstance();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if (!id2field2method.containsKey(entity.getKey()))
			generateClassStructureBuffer(entity);
		deserialize(entity, data, writable);
		return entity;
	}

	public static ISerizlizable deserialize(ISerizlizable entity, byte[] data,
			Integer... writable) {

		Map<Field, IWriteValue> field2func = id2field2method.get(entity
				.getKey());
		Map<Field, Integer> field2writableindex = id2field2writableindex
				.get(entity.getKey());
		if (field2func == null || field2writableindex == null) {
			Logger.getRootLogger().error("unknown cmdid pattern!");
		} else {
			ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(data);
			List<Integer> list = (writable == null || writable.length == 0) ? null
					: Arrays.asList(writable);
			for (Field field : field2func.keySet()) {
				if (writable == null || writable.length == 0
						|| list.contains(field2writableindex.get(field)))
					field2func.get(field).readValue(buffer, entity);
			}
		}
		return entity;
	}
}
